﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using TLM.VsmdiReader.Exceptions;
using TLM.VsmdiReader.Properties;

namespace TLM.VsmdiReader
{
    /// <summary>
    /// This class contains all the logic for parsing vsmdi files into VSMDI objects.
    /// </summary>
    public class VsmdiParser
    {
        private const string LIST_OF_TESTS_GUID = "8c43106b-9dc1-4907-a29f-aa66a61bf5b6";

        private string _vsdmiFile = null;
        private List<XElement> _testListsInVsmdiFile;
        private XNamespace _namespace;
       
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="vsdmiFile">The full path to the vsmdi file to handle</param>
        public VsmdiParser(string vsdmiFile)
        {
            if (File.Exists(vsdmiFile))
            {
                _vsdmiFile = vsdmiFile;
            }
            else
            {
                throw new FileNotFoundException("Vsdmi file not found: " + vsdmiFile);
            }
        }

        /// <summary>
        /// Validate the vsmdi file against the XSD-schema
        /// </summary>
        /// <returns>True if the vsmdi file is valid accoording to the XSD-schema otherwise false</returns>
        public bool ValidateVsmdi()
        {
            XmlSchemaSet schemaSet = CreateXmlSchemaSet();
            bool isValid = true;

            XDocument xDocument = XDocument.Load(_vsdmiFile);
            xDocument.Validate(schemaSet, (o, e) =>
                                            {
                                                throw e.Exception;
                                            }, true);       
            return isValid;           
        }

        /// <summary>
        /// Parse the vsmdi file
        /// </summary>
        /// <returns>A <see cref="VsmdiFile"/> containing all the information of the vsmdi file</returns>
        public VsmdiFile ParseVsmdiFile()
        {
            XDocument xDocVsmdiFile = XDocument.Load(_vsdmiFile);
            _namespace = xDocVsmdiFile.Root.Name.Namespace;
            
            _testListsInVsmdiFile = xDocVsmdiFile.Descendants(_namespace + "TestList").ToList();

            var listOfTests =_testListsInVsmdiFile.FirstOrDefault(testList => 
                string.Equals((string)testList.Attribute("id"), LIST_OF_TESTS_GUID, StringComparison.OrdinalIgnoreCase));
            
            if (listOfTests == null)
            {
                throw new ListOfTestsNotFoundException("The root test list (List of tests) couldn't be found");
            }
            
            VsmdiFile vsmdiFile = new VsmdiFile();
            vsmdiFile.ListOfTests = CreateTestList(listOfTests);
            vsmdiFile.FileName = Path.GetFileName(_vsdmiFile);

            return vsmdiFile;
        }

        /// <summary>
        /// Create a <see cref="TestList"/> object containing all the information (including child lists and testlinks)
        /// </summary>
        /// <param name="xmlTestListElement">The testlist XmllElement from the vsmdi file</param>
        /// <returns>A <see cref="TestList"/> object contain all the information</returns>
        private TestList CreateTestList(XElement xmlTestListElement)
        {
            if (xmlTestListElement == null)
            {
                throw new ArgumentNullException("xmlTestListElement", "Correct TestLists element couldn't be found.");
            }
      
            TestList testList = new TestList();

            Guid? id = (Guid?)xmlTestListElement.Attribute("id");
            if (id == null)
            {
                throw new MissingAttributeException("The id attribute couldn't be found in the test list element");
            }
            testList.Id = id.Value;

            string testLinkName = (string)xmlTestListElement.Attribute("name");
            if (testLinkName == null)
            {
                throw new MissingAttributeException("The name attribute couldn't be found in the test list element");
            }
            testList.Name = testLinkName;

            testList.Enabled = (bool?)xmlTestListElement.Attribute("enabled") ?? true;

            testList.ChildLists = GetChildListsByParent(testList.Id);
            testList.TestLinks = GetTestLinksByTestList(xmlTestListElement);
            
            return testList;
        }

        /// <summary>
        /// Get all child test lists of a giving guid (parent list)
        /// </summary>
        /// <param name="parentListId">The guid of the test list to get the childs off</param>
        /// <returns>A IEnumerable of <see cref="TestList"/> objects</returns>
        private IEnumerable<TestList> GetChildListsByParent(Guid parentListId)
        {
            var childLists = _testListsInVsmdiFile
                .Where(testList => string.Equals((string)testList.Attribute("parentListId"), parentListId.ToString(), StringComparison.OrdinalIgnoreCase))
                .Select(testList => CreateTestList(testList))
                .ToList();

            return childLists;
        }

        /// <summary>
        /// Get all the testlinks by a <see cref="TestList"/>
        /// </summary>
        /// <param name="testListElement">The TestList XmlElement from the vsmdi file</param>
        /// <returns>A IEnumerable of <see cref="TestLink"/> objects containing all the tests in a testlist</returns>
        private IEnumerable<TestLink> GetTestLinksByTestList(XElement testListElement)
        {
            List<TestLink> testLinks = testListElement.Descendants(_namespace + "TestLink")
                .Select(testLink => CreateTesLink(testLink))
                .ToList();

            return testLinks;
        }

        /// <summary>
        /// Create a <see cref="TestLink"/> object that contains the information of a test in a testlist
        /// </summary>
        /// <param name="xmlTestLinkElement">The testlink XmllElement from the vsmdi file</param>
        /// <returns>A <see cref="TestLink"/> object containing the information of the testlist</returns>
        private TestLink CreateTesLink(XElement xmlTestLinkElement)
        {
            if (xmlTestLinkElement == null)
            {
                throw new ArgumentNullException("xmlTestLinkElement", "Correct TestLink element couldn't be found.");
            }

            TestLink testLink = new TestLink();
            
            Guid? id = (Guid?)xmlTestLinkElement.Attribute("id");
            if (id == null)
            {
                throw new MissingAttributeException("The id attribute couldn't be found in the test link element");
            }

            testLink.Id = id.Value;
            testLink.Name = (string)xmlTestLinkElement.Attribute("name"); ; 
            testLink.Enabled = (bool?)xmlTestLinkElement.Attribute("enabled") ?? true;

            return testLink;
        }

        /// <summary>
        /// Create a XmlSchemaSet to validate the vsmdi files against
        /// </summary>
        /// <returns>A <see cref="XmlSchemaSet"/> that contain both schema versions</returns>
        private static XmlSchemaSet CreateXmlSchemaSet()
        {
            XmlSchemaSet schemaSet = new XmlSchemaSet();


            using (StringReader vstst_2006Reader = new StringReader(Resources.vstst_2006))
            using (StringReader vstst_2010Reader = new StringReader(Resources.vstst_2010))
            {
                schemaSet.Add(@"http://microsoft.com/schemas/VisualStudio/TeamTest/2006", XmlReader.Create(vstst_2006Reader));
                schemaSet.Add(@"http://microsoft.com/schemas/VisualStudio/TeamTest/2010", XmlReader.Create(vstst_2010Reader));
            }
            return schemaSet;
        }
    }
}